{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a href=\"https://colab.research.google.com/github/microsoft/autogen/blob/main/notebook/agentchat_compression.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Auto Generated Agent Chat: Conversations with Chat History Compression Enabled (Experimental)\n",
    "\n",
    "AutoGen offers conversable agents powered by LLM, tools, or humans, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participance through multi-agent conversation. Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n",
    "\n",
    "In this notebook, we demonstrate how to enable compression of history messages using the `CompressibleAgent`. While this agent retains all the default functionalities of the `AssistantAgent`, it also provides the added feature of compression when activated through the `compress_config` setting.\n",
    "\n",
    "Different compression modes are supported:\n",
    "1. `compress_config=False` (Default): `CompressibleAgent` is equivalent to `AssistantAgent`.\n",
    "2. `compress_config=True` or `compress_config={\"mode\": \"TERMINATE\"}`: no compression will be performed. However, we will count token usage before sending requests to the OpenAI model. The conversation will be terminated directly if the total token usage exceeds the maximum token usage allowed by the model (to avoid the token limit error from OpenAI API).\n",
    "3. `compress_config={\"mode\": \"COMPRESS\", \"trigger_count\": <your pre-set number>}, \"leave_last_n\": <your pre-set number>`: compression is enabled.\n",
    "    ```python\n",
    "    # default compress_config\n",
    "    compress_config = {\n",
    "        \"mode\": \"COMPRESS\",\n",
    "        \"compress_function\": None,\n",
    "        \"trigger_count\": 0.7, # default to 0.7, or your pre-set number\n",
    "        \"broadcast\": True, # the compressed with be broadcast to sender. This will not be used in groupchat.\n",
    "\n",
    "        # the following settings are for this mode only\n",
    "        \"leave_last_n\": 2, # leave the last n messages in the history to avoid compression\n",
    "        \"verbose\": False, # if True, print out the content to be compressed and the compressed content\n",
    "    }\n",
    "    ```\n",
    "    Currently, our compression logic is as follows:\n",
    "        1. We will always leave the first user message (as well as system prompts) and compress the rest of the history messages.\n",
    "        2. You can choose to not compress the last n messages in the history with \"leave_last_n\".\n",
    "        2. The summary is performed on a per-message basis, with the role of the messages (See compressed content in the example below).\n",
    "\n",
    "4. `compress_config={\"mode\": \"CUSTOMIZED\", \"compress_function\": <A customized function for compression>}`: the `compress_function` function will be called on trigger count. The function should accept a list of messages as input and return a tuple of (is_success: bool, compressed_messages: List[Dict]). The whole message history (except system prompt) will be passed.\n",
    "\n",
    "\n",
    "By adjusting `trigger_count`, you can decide when to compress the history messages based on existing tokens. If this is a float number between 0 and 1, it is interpreted as a ratio of max tokens allowed by the model. For example, the AssistantAgent uses gpt-4 with max tokens 8192, the trigger_count = 0.7 * 8192 = 5734.4 -> 5734. Do not set `trigger_count` to the max tokens allowed by the model, since the same LLM is employed for compression and it needs tokens to generate the compressed content. \n",
    "\n",
    "\n",
    "\n",
    "## Limitations\n",
    "- For now, the compression feature **is not well-supported for groupchat**. If you initialize a `CompressibleAgent` in a groupchat with compression, the compressed cannot be broadcast to all other agents in the groupchat. If you use this feature in groupchat, extra cost will be incurred since compression will be performed on at per-agent basis.\n",
    "- We do not support async compression for now.\n",
    "\n",
    "## Requirements\n",
    "\n",
    "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n",
    "```bash\n",
    "pip install pyautogen\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %pip install pyautogen~=0.1.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set your API Endpoint\n",
    "\n",
    "The [`config_list_from_json`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_from_json) function loads a list of configurations from an environment variable or a json file.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define functions according to the function description\n",
    "from IPython import get_ipython\n",
    "\n",
    "import autogen\n",
    "from autogen.agentchat.contrib.compressible_agent import CompressibleAgent\n",
    "from autogen.agentchat.contrib.math_user_proxy_agent import MathUserProxyAgent\n",
    "\n",
    "config_list = autogen.config_list_from_json(\n",
    "    \"OAI_CONFIG_LIST\",\n",
    "    filter_dict={\n",
    "        \"model\": [\"gpt-4\", \"gpt-4-0314\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well).\n",
    "\n",
    "The config list looks like the following:\n",
    "```python\n",
    "config_list = [\n",
    "    {\n",
    "        'model': 'gpt-4',\n",
    "        'api_key': '<your OpenAI API key here>',\n",
    "    },\n",
    "    {\n",
    "        'model': 'gpt-4',\n",
    "        'api_key': '<your Azure OpenAI API key here>',\n",
    "        'base_url': '<your Azure OpenAI API base here>',\n",
    "        'api_type': 'azure',\n",
    "        'api_version': '2023-06-01-preview',\n",
    "    },\n",
    "    {\n",
    "        'model': 'gpt-4-32k',\n",
    "        'api_key': '<your Azure OpenAI API key here>',\n",
    "        'base_url': '<your Azure OpenAI API base here>',\n",
    "        'api_type': 'azure',\n",
    "        'api_version': '2023-06-01-preview',\n",
    "    },\n",
    "]\n",
    "```\n",
    "\n",
    "If you open this notebook in colab, you can upload your files by clicking the file icon on the left panel and then choose \"upload file\" icon.\n",
    "\n",
    "You can set the value of config_list in other ways you prefer, e.g., loading from a YAML file."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 1\n",
    "This example is from [agentchat_MathChat.ipynb](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_MathChat.ipynb). Compression with code execution.\n",
    "\n",
    "Note: we set `trigger_count=600`, and `leave_last_n=2`. In this example, we set a low trigger_count to demonstrate the compression feature. \n",
    "The token count after compression is still bigger than trigger count, mainly because the trigger count is low an the first and last 2 messages are not compressed. Thus, the compression is performed at each turn. In practice, you want to adjust the trigger_count to a bigger number and properly set the `leave_last_n` to avoid compression at each turn. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
      "\n",
      "Let's use Python to solve a math problem.\n",
      "\n",
      "Query requirements:\n",
      "You should always use the 'print' function for the output and use fractions/radical forms instead of decimals.\n",
      "You can use packages like sympy to help you.\n",
      "You must follow the formats below to write your code:\n",
      "```python\n",
      "# your code\n",
      "```\n",
      "\n",
      "First state the key idea to solve the problem. You may choose from three ways to solve the problem:\n",
      "Case 1: If the problem can be solved with Python code directly, please write a program to solve it. You can enumerate all possible arrangements if needed.\n",
      "Case 2: If the problem is mostly reasoning, you can solve it by yourself directly.\n",
      "Case 3: If the problem cannot be handled in the above two ways, please follow this process:\n",
      "1. Solve the problem step by step (do not over-divide the steps).\n",
      "2. Take out any queries that can be asked through Python (for example, any calculations or equations that can be calculated).\n",
      "3. Wait for me to give the results.\n",
      "4. Continue if you think the result is correct. If the result is invalid or unexpected, please correct your query or reasoning.\n",
      "\n",
      "After all the queries are run and you get the answer, put the answer in \\boxed{}.\n",
      "\n",
      "Problem:\n",
      "Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
      "\n",
      "First, we need to consider both sides of the inequality as two separate equations. Then subtract one from the other to set this result equal to zero. This would allow us to find the critical points, i.e., the places where the inequality changes its nature (from less to more or vice versa). Then we find the intervals.\n",
      "\n",
      "Here's how you can solve this problem in Python:\n",
      "\n",
      "```python\n",
      "from sympy import *\n",
      "from sympy.abc import x\n",
      "\n",
      "# define the equation\n",
      "equation = (2*x+10)*(x+3) - (3*x+9)*(x+8)\n",
      "\n",
      "# set the equation equal to zero to find the critical points\n",
      "critical_points = solve(equation, x)\n",
      "\n",
      "# Sort the critical_points\n",
      "critical_points = sorted(critical_points)\n",
      "\n",
      "# define a function to test the intervals\n",
      "def test_intervals(interval):\n",
      "    test_num = sum(interval)/2  # get the mid point of the interval\n",
      "    return equation.subs(x, test_num)\n",
      "\n",
      "# define the intervals based on the critical points\n",
      "intervals = [(-oo, critical_points[0]), (critical_points[0], critical_points[1]), (critical_points[1], oo)]\n",
      "\n",
      "solution = []\n",
      "\n",
      "# loop through the intervals, if the result is less than zero, it means it's a valid interval\n",
      "for i in intervals:\n",
      "    if test_intervals(i) < 0:\n",
      "        solution.append(i)\n",
      "\n",
      "# print the solution in interval notation\n",
      "for interval in solution:\n",
      "    print(interval)\n",
      "```\n",
      "\n",
      "Replace oo with infinity when interpreting the result. Also, keep in mind that in interval notation, parentheses denote that the endpoint is not included in the set, and brackets denote that the end point is included in the set. Thus, (a, b) means \"greater than a and less than b\", [a, b] means \"greater than or equal to a and less than or equal to b\".\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
      "\n",
      "Error: Traceback (most recent call last):\n",
      "  File \"\", line 25, in <module>\n",
      "    if test_intervals(i) < 0:\n",
      "      return func(self, other)\n",
      "      return StrictLessThan(self, other)\n",
      "      raise TypeError(\"Invalid NaN comparison\")\n",
      "TypeError: Invalid NaN comparison\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Warning: Compression skipped at trigger count threshold. The first msg and last 2 msgs will not be compressed. current msg count: 3. Consider raising trigger_count.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
      "\n",
      "I apologize for the oversight. It seems like the function is returning NaN error. Because we are dealing with real numbers, NaN (Not a Number) error could arise from operations that don't return a defined real number. \n",
      "\n",
      "However, in this case, we it seems there's a problem with the comparison of symbolic terms with zero in the function test_intervals.\n",
      "\n",
      "Let's correct this approach. The error arises from trying to compare a symbolic expression to zero directly. Instead, we can create a numeric function from our symbolic expression, and use this function to test the intervals. \n",
      "\n",
      "Here is the revised code:\n",
      "\n",
      "```python\n",
      "from sympy import *\n",
      "from sympy.abc import x\n",
      "\n",
      "# define the equation\n",
      "equation = expand((2*x+10)*(x+3) - (3*x+9)*(x+8))\n",
      "\n",
      "# set the equation equal to zero to find the critical points\n",
      "critical_points = solve(equation, x)\n",
      "\n",
      "# Convert the points to standard python floats, so that they can be sorted numerically\n",
      "critical_points = [N(i) for i in critical_points]\n",
      "\n",
      "# Sort the critical_points\n",
      "critical_points = sorted(critical_points)\n",
      "\n",
      "# define a function to test the intervals\n",
      "def test_intervals(interval):\n",
      "  test_num = N(sum(interval)/2)  # get the mid point of the interval\n",
      "  return equation.subs(x, test_num)\n",
      "\n",
      "# define the intervals based on the critical points\n",
      "intervals = [(-oo, critical_points[0]), (critical_points[0], critical_points[1]), (critical_points[1], oo)]\n",
      "\n",
      "# Now, let's create a numeric function from this symbolic equation\n",
      "numeric_equation = lambdify(x, equation)\n",
      "\n",
      "solution = []\n",
      "\n",
      "# loop through the intervals, first find the mid point of each interval, \n",
      "# then if numeric result for this mid point is less than zero, it means it's a valid interval\n",
      "for interval in intervals:\n",
      "    mid_point = sum(interval) / 2  \n",
      "    if numeric_equation(mid_point) < 0:\n",
      "        solution.append(interval)\n",
      "\n",
      "# print the solution in interval notation\n",
      "for interval in solution:\n",
      "    print(interval)\n",
      "```\n",
      "\n",
      "Remember to replace `oo` with infinity while interpreting the output.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
      "\n",
      "Error: Traceback (most recent call last):\n",
      "  File \"\", line 36, in <module>\n",
      "    if numeric_equation(mid_point) < 0:\n",
      "      return func(self, other)\n",
      "      return StrictLessThan(self, other)\n",
      "      raise TypeError(\"Invalid NaN comparison\")\n",
      "TypeError: Invalid NaN comparison\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
      "To be compressed:\n",
      "##ASSISTANT## First, we need to consider both sides of the inequality as two separate equations. Then subtract one from the other to set this result equal to zero. This would allow us to find the critical points, i.e., the places where the inequality changes its nature (from less to more or vice versa). Then we find the intervals.\n",
      "\n",
      "Here's how you can solve this problem in Python:\n",
      "\n",
      "```python\n",
      "from sympy import *\n",
      "from sympy.abc import x\n",
      "\n",
      "# define the equation\n",
      "equation = (2*x+10)*(x+3) - (3*x+9)*(x+8)\n",
      "\n",
      "# set the equation equal to zero to find the critical points\n",
      "critical_points = solve(equation, x)\n",
      "\n",
      "# Sort the critical_points\n",
      "critical_points = sorted(critical_points)\n",
      "\n",
      "# define a function to test the intervals\n",
      "def test_intervals(interval):\n",
      "    test_num = sum(interval)/2  # get the mid point of the interval\n",
      "    return equation.subs(x, test_num)\n",
      "\n",
      "# define the intervals based on the critical points\n",
      "intervals = [(-oo, critical_points[0]), (critical_points[0], critical_points[1]), (critical_points[1], oo)]\n",
      "\n",
      "solution = []\n",
      "\n",
      "# loop through the intervals, if the result is less than zero, it means it's a valid interval\n",
      "for i in intervals:\n",
      "    if test_intervals(i) < 0:\n",
      "        solution.append(i)\n",
      "\n",
      "# print the solution in interval notation\n",
      "for interval in solution:\n",
      "    print(interval)\n",
      "```\n",
      "\n",
      "Replace oo with infinity when interpreting the result. Also, keep in mind that in interval notation, parentheses denote that the endpoint is not included in the set, and brackets denote that the end point is included in the set. Thus, (a, b) means \"greater than a and less than b\", [a, b] means \"greater than or equal to a and less than or equal to b\".\n",
      "##USER## Error: Traceback (most recent call last):\n",
      "  File \"\", line 25, in <module>\n",
      "    if test_intervals(i) < 0:\n",
      "      return func(self, other)\n",
      "      return StrictLessThan(self, other)\n",
      "      raise TypeError(\"Invalid NaN comparison\")\n",
      "TypeError: Invalid NaN comparison\n",
      "\n",
      "\u001b[35m******************************Content after compressing:******************************\u001b[0m\n",
      "##ASSISTANT##\n",
      "The assistant provides instructions to solve an inequality, which involves considering separate equations for the inequality components, finding the differing point (set to zero), identifying critical points (where nature of inequality changes), and determining the intervals. \n",
      "\n",
      "They also share Python code for the process, using sympy library. Steps include defining the equation, finding critical points by setting equation to zero, sorting these points, defining a function to test the intervals, determining intervals based on critical points, and looping through intervals to identify valid solutions. The assistant defines 'oo' as infinity and clarifies the use of parentheses and brackets to denote exclusion or inclusion of endpoints in interval notation.\n",
      "\n",
      "##CODE##\n",
      "Python script to solve the given inequality and display the solution using interval notation.\n",
      "\n",
      "##USER##\n",
      "User encounters 'TypeError: Invalid NaN comparison', indicating there's an error when comparing a test interval to zero in the Python script shared by the assistant.\n",
      " \u001b[35m\n",
      "********************************************************************************\u001b[0m\n",
      "\u001b[35mToken Count (including 13 tokens from system msg and function descriptions). Before compression : 1294 | After: 1045\u001b[0m\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
      "\n",
      "I apologize for the confusion. There's an error with the calculation of mid_point when the interval is from negative infinity to a real number or a real number to infinity. In these circumstances, the calculation will return a NaN which cannot be used in comparison. We could handle these issues specifically.\n",
      "\n",
      "Let's refine the script:\n",
      "\n",
      "```python\n",
      "from sympy import *\n",
      "from sympy.abc import x\n",
      "\n",
      "# define the equation\n",
      "equation = expand((2*x+10)*(x+3) - (3*x+9)*(x+8))\n",
      "\n",
      "# set the equation equal to zero to find the critical points\n",
      "critical_points = solve(equation, x)\n",
      "\n",
      "# Convert the points to standard python floats, so that they can be sorted numerically\n",
      "critical_points = [N(i) for i in critical_points]\n",
      "\n",
      "# Sort the critical_points\n",
      "critical_points = sorted(critical_points)\n",
      "\n",
      "# Now, let's create a numeric function from this symbolic equation\n",
      "numeric_equation = lambdify(x, equation)\n",
      "\n",
      "solution = []\n",
      "\n",
      "# Define the intervals based on the critical points\n",
      "intervals = [(-oo, critical_points[0]), (critical_points[0], critical_points[1]), (critical_points[1], oo)]\n",
      "\n",
      "# Loop through the intervals and compute the mid point of each interval\n",
      "# If the mid point is NaN (occurs when dealing with infinity), then specific values are used for testing\n",
      "for interval in intervals:\n",
      "    if interval[0] == -oo:\n",
      "        test_value = interval[1] - 1\n",
      "    elif interval[1] == oo:\n",
      "        test_value = interval[0] + 1\n",
      "    else:\n",
      "        test_value = sum(interval) / 2\n",
      "\n",
      "    # If numeric result for test_value is less than zero, it's a valid interval\n",
      "    if numeric_equation(test_value) < 0:\n",
      "        solution.append(interval)\n",
      "\n",
      "# Print out the solution intervals\n",
      "for interval in solution:\n",
      "    print(interval)\n",
      "```\n",
      "\n",
      "The script now handles the special cases when dealing with infinity and should return the valid intervals.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
      "\n",
      "(-oo, -14.0000000000000)\n",
      "(-3.00000000000000, oo)\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
      "To be compressed:\n",
      "##ASSISTANT##\n",
      "The assistant provides instructions to solve an inequality, which involves considering separate equations for the inequality components, finding the differing point (set to zero), identifying critical points (where nature of inequality changes), and determining the intervals. \n",
      "\n",
      "They also share Python code for the process, using sympy library. Steps include defining the equation, finding critical points by setting equation to zero, sorting these points, defining a function to test the intervals, determining intervals based on critical points, and looping through intervals to identify valid solutions. The assistant defines 'oo' as infinity and clarifies the use of parentheses and brackets to denote exclusion or inclusion of endpoints in interval notation.\n",
      "\n",
      "##CODE##\n",
      "Python script to solve the given inequality and display the solution using interval notation.\n",
      "\n",
      "##USER##\n",
      "User encounters 'TypeError: Invalid NaN comparison', indicating there's an error when comparing a test interval to zero in the Python script shared by the assistant.\n",
      "\n",
      "##ASSISTANT## I apologize for the oversight. It seems like the function is returning NaN error. Because we are dealing with real numbers, NaN (Not a Number) error could arise from operations that don't return a defined real number. \n",
      "\n",
      "However, in this case, we it seems there's a problem with the comparison of symbolic terms with zero in the function test_intervals.\n",
      "\n",
      "Let's correct this approach. The error arises from trying to compare a symbolic expression to zero directly. Instead, we can create a numeric function from our symbolic expression, and use this function to test the intervals. \n",
      "\n",
      "Here is the revised code:\n",
      "\n",
      "```python\n",
      "from sympy import *\n",
      "from sympy.abc import x\n",
      "\n",
      "# define the equation\n",
      "equation = expand((2*x+10)*(x+3) - (3*x+9)*(x+8))\n",
      "\n",
      "# set the equation equal to zero to find the critical points\n",
      "critical_points = solve(equation, x)\n",
      "\n",
      "# Convert the points to standard python floats, so that they can be sorted numerically\n",
      "critical_points = [N(i) for i in critical_points]\n",
      "\n",
      "# Sort the critical_points\n",
      "critical_points = sorted(critical_points)\n",
      "\n",
      "# define a function to test the intervals\n",
      "def test_intervals(interval):\n",
      "  test_num = N(sum(interval)/2)  # get the mid point of the interval\n",
      "  return equation.subs(x, test_num)\n",
      "\n",
      "# define the intervals based on the critical points\n",
      "intervals = [(-oo, critical_points[0]), (critical_points[0], critical_points[1]), (critical_points[1], oo)]\n",
      "\n",
      "# Now, let's create a numeric function from this symbolic equation\n",
      "numeric_equation = lambdify(x, equation)\n",
      "\n",
      "solution = []\n",
      "\n",
      "# loop through the intervals, first find the mid point of each interval, \n",
      "# then if numeric result for this mid point is less than zero, it means it's a valid interval\n",
      "for interval in intervals:\n",
      "    mid_point = sum(interval) / 2  \n",
      "    if numeric_equation(mid_point) < 0:\n",
      "        solution.append(interval)\n",
      "\n",
      "# print the solution in interval notation\n",
      "for interval in solution:\n",
      "    print(interval)\n",
      "```\n",
      "\n",
      "Remember to replace `oo` with infinity while interpreting the output.\n",
      "##USER## Error: Traceback (most recent call last):\n",
      "  File \"\", line 36, in <module>\n",
      "    if numeric_equation(mid_point) < 0:\n",
      "      return func(self, other)\n",
      "      return StrictLessThan(self, other)\n",
      "      raise TypeError(\"Invalid NaN comparison\")\n",
      "TypeError: Invalid NaN comparison\n",
      "\n",
      "\u001b[35m******************************Content after compressing:******************************\u001b[0m\n",
      "##ASSISTANT##\n",
      "The assistant provides steps to solve an inequality, including considering separate equations, finding the differing point (set to zero), identifying critical points, and determining intervals. They provide Python code using sympy for the process, defining 'oo' as infinity and explaining usage of parentheses and brackets in interval notation. \n",
      "\n",
      "##CODE##\n",
      "The assistant provides Python code for solving an inequality and displaying the solution in interval notation.\n",
      "\n",
      "##USER##\n",
      "The user reports encountering a 'TypeError: Invalid NaN comparison' when comparing a test interval to zero in the script provided by the assistant.\n",
      "\n",
      "##ASSISTANT##\n",
      "The assistant apologizes for the oversight and suggests the issue might lie in the comparison of symbolic terms with zero. The assistant then provides revised code, explaining it creates a numeric function from the symbolic expression to test the intervals. It's highlighted to replace 'oo' with infinity when interpreting the output.\n",
      "\n",
      "##CODE##\n",
      "Revised Python code is given by the assistant to solve a TypeError that occurred in the initial code.\n",
      "\n",
      "##USER##\n",
      "The user reports another TypeError ('Invalid NaN comparison') when executing the revised code provided by the assistant. \u001b[35m\n",
      "********************************************************************************\u001b[0m\n",
      "\u001b[35mToken Count (including 13 tokens from system msg and function descriptions). Before compression : 1494 | After: 1013\u001b[0m\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
      "\n",
      "Great! You have got the correct answer.\n",
      "\n",
      "So, for $x$ in the interval $(-\\infty, -14]$ and $(-3, +\\infty)$, the inequality $(2x+10)(x+3)<(3x+9)(x+8)$ is satisfied. \n",
      "\n",
      "In interval notation, this result can be written as $(-\\infty, -14] \\cup (-3, +\\infty)$.\n",
      "\n",
      "The final answer is:\n",
      "\n",
      "\\boxed{(-\\infty, -14] \\cup (-3, +\\infty)}\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# 1. replace AssistantAgent with CompressibleAgent\n",
    "assistant = CompressibleAgent(\n",
    "    name=\"assistant\",\n",
    "    system_message=\"You are a helpful assistant.\",\n",
    "    llm_config={\n",
    "        \"timeout\": 600,\n",
    "        \"cache_seed\": 42,\n",
    "        \"config_list\": config_list,\n",
    "    },\n",
    "    compress_config={\n",
    "        \"mode\": \"COMPRESS\",\n",
    "        \"trigger_count\": 600,  # set this to a large number for less frequent compression\n",
    "        \"verbose\": True,  # to allow printing of compression information: context before and after compression\n",
    "        \"leave_last_n\": 2,\n",
    "    },\n",
    ")\n",
    "\n",
    "# 2. create the MathUserProxyAgent instance named \"mathproxyagent\"\n",
    "mathproxyagent = MathUserProxyAgent(\n",
    "    name=\"mathproxyagent\",\n",
    "    human_input_mode=\"NEVER\",\n",
    "    code_execution_config={\n",
    "        \"use_docker\": False\n",
    "    },  # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
    "    max_consecutive_auto_reply=5,\n",
    ")\n",
    "math_problem = (\n",
    "    \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\"\n",
    ")\n",
    "mathproxyagent.initiate_chat(assistant, problem=math_problem)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 2\n",
    "This example is from [agentchat_function_call.ipynb](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_function_call.ipynb). Compression with function calls. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
      "\n",
      "Draw two agents chatting with each other with an example dialog. Don't add plt.show().\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
      "\n",
      "\u001b[32m***** Suggested function Call: python *****\u001b[0m\n",
      "Arguments: \n",
      "{\n",
      "  \"cell\": \n",
      "  \"import matplotlib.pyplot as plt\n",
      "\n",
      "  # Define agent texts\n",
      "  agent1_texts = ['Hello there!', 'Nice to meet you.', 'How can I assist you?']\n",
      "  agent2_texts = ['Hey!', 'Nice meeting you too.', 'Could you help me solve a problem?']\n",
      "\n",
      "  # Define agent y positions\n",
      "  agent1_y = [3, 2, 1]\n",
      "  agent2_y = [3, 2, 1]\n",
      "\n",
      "  # Create figure and axis\n",
      "  fig, ax = plt.subplots()\n",
      "\n",
      "  # Plot Agent 1 texts\n",
      "  for i, text in enumerate(agent1_texts):\n",
      "      ax.text(0, agent1_y[i], text, fontsize=12, ha='right')\n",
      "\n",
      "  # Plot Agent 2 texts\n",
      "  for i, text in enumerate(agent2_texts):\n",
      "      ax.text(1, agent2_y[i], text, fontsize=12, ha='left')\n",
      "\n",
      "  # Set plot attributes\n",
      "  ax.set_xlim(-0.5, 1.5)\n",
      "  ax.set_ylim(0, 4)\n",
      "  ax.set_xticks([0, 1])\n",
      "  ax.set_xticklabels(['Agent 1', 'Agent 2'])\n",
      "  ax.set_yticks([])\n",
      "  ax.spines['top'].set_visible(False)\n",
      "  ax.spines['right'].set_visible(False)\n",
      "  ax.spines['bottom'].set_visible(False)\"\n",
      "}\n",
      "\u001b[32m*******************************************\u001b[0m\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35m\n",
      ">>>>>>>> EXECUTING FUNCTION python...\u001b[0m\n"
     ]
    },
    {
     "ename": "IndentationError",
     "evalue": "unexpected indent (1440792568.py, line 4)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;36m  Cell \u001b[0;32mIn[3], line 4\u001b[0;36m\u001b[0m\n\u001b[0;31m    agent1_texts = ['Hello there!', 'Nice to meet you.', 'How can I assist you?']\u001b[0m\n\u001b[0m    ^\u001b[0m\n\u001b[0;31mIndentationError\u001b[0m\u001b[0;31m:\u001b[0m unexpected indent\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
      "\n",
      "\u001b[32m***** Response from calling function \"python\" *****\u001b[0m\n",
      "None\n",
      "unexpected indent (1440792568.py, line 4)\n",
      "\u001b[32m***************************************************\u001b[0m\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
      "\n",
      "\u001b[32m***** Suggested function Call: python *****\u001b[0m\n",
      "Arguments: \n",
      "{\n",
      "  \"cell\": \n",
      "  \"import matplotlib.pyplot as plt\n",
      "\n",
      "# Define agent texts\n",
      "agent1_texts = ['Hello there!', 'Nice to meet you.', 'How can I assist you?']\n",
      "agent2_texts = ['Hey!', 'Nice meeting you too.', 'Could you help me solve a problem?']\n",
      "\n",
      "# Define agent y positions\n",
      "agent1_y = [3, 2, 1]\n",
      "agent2_y = [3, 2, 1]\n",
      "\n",
      "# Create figure and axis\n",
      "fig, ax = plt.subplots()\n",
      "\n",
      "# Plot Agent 1 texts\n",
      "for i, text in enumerate(agent1_texts):\n",
      "    ax.text(0, agent1_y[i], text, fontsize=12, ha='right')\n",
      "\n",
      "# Plot Agent 2 texts\n",
      "for i, text in enumerate(agent2_texts):\n",
      "    ax.text(1, agent2_y[i], text, fontsize=12, ha='left')\n",
      "\n",
      "# Set plot attributes\n",
      "ax.set_xlim(-0.5, 1.5)\n",
      "ax.set_ylim(0, 4)\n",
      "ax.set_xticks([0, 1])\n",
      "ax.set_xticklabels(['Agent 1', 'Agent 2'])\n",
      "ax.set_yticks([])\n",
      "ax.spines['top'].set_visible(False)\n",
      "ax.spines['right'].set_visible(False)\n",
      "ax.spines['bottom'].set_visible(False)\"\n",
      "}\n",
      "\u001b[32m*******************************************\u001b[0m\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35m\n",
      ">>>>>>>> EXECUTING FUNCTION python...\u001b[0m\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
      "\n",
      "\u001b[32m***** Response from calling function \"python\" *****\u001b[0m\n",
      "None\n",
      "\u001b[32m***************************************************\u001b[0m\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
      "To be compressed:\n",
      "##FUNCTION_CALL## \n",
      "Name: python\n",
      "Args: {\n",
      "  \"cell\": \n",
      "  \"import matplotlib.pyplot as plt\n",
      "\n",
      "  # Define agent texts\n",
      "  agent1_texts = ['Hello there!', 'Nice to meet you.', 'How can I assist you?']\n",
      "  agent2_texts = ['Hey!', 'Nice meeting you too.', 'Could you help me solve a problem?']\n",
      "\n",
      "  # Define agent y positions\n",
      "  agent1_y = [3, 2, 1]\n",
      "  agent2_y = [3, 2, 1]\n",
      "\n",
      "  # Create figure and axis\n",
      "  fig, ax = plt.subplots()\n",
      "\n",
      "  # Plot Agent 1 texts\n",
      "  for i, text in enumerate(agent1_texts):\n",
      "      ax.text(0, agent1_y[i], text, fontsize=12, ha='right')\n",
      "\n",
      "  # Plot Agent 2 texts\n",
      "  for i, text in enumerate(agent2_texts):\n",
      "      ax.text(1, agent2_y[i], text, fontsize=12, ha='left')\n",
      "\n",
      "  # Set plot attributes\n",
      "  ax.set_xlim(-0.5, 1.5)\n",
      "  ax.set_ylim(0, 4)\n",
      "  ax.set_xticks([0, 1])\n",
      "  ax.set_xticklabels(['Agent 1', 'Agent 2'])\n",
      "  ax.set_yticks([])\n",
      "  ax.spines['top'].set_visible(False)\n",
      "  ax.spines['right'].set_visible(False)\n",
      "  ax.spines['bottom'].set_visible(False)\"\n",
      "}\n",
      "##FUNCTION_RETURN## (from function \"python\"): \n",
      "None\n",
      "unexpected indent (1440792568.py, line 4)\n",
      "\n",
      "\u001b[35m******************************Content after compressing:******************************\u001b[0m\n",
      "##FUNCTION_CALL##\n",
      "Name: python\n",
      "Args: Executing a block of Python code that imports the matplotlib.pyplot library for graphing and plotting. It defines texts for two agents and sets y positions. It also creates a figure and plots the agent texts on a graph with specific attributes.\n",
      " \n",
      "##FUNCTION_RETURN## (from function \"python\"):\n",
      "None. Execution failed due to an unexpected indentation error at line 4.\n",
      " \u001b[35m\n",
      "********************************************************************************\u001b[0m\n",
      "\u001b[35mToken Count (including 107 tokens from system msg and function descriptions). Before compression : 821 | After: 564\u001b[0m\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
      "\n",
      "TERMINATE\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "llm_config = {\n",
    "    \"functions\": [\n",
    "        {\n",
    "            \"name\": \"python\",\n",
    "            \"description\": \"run cell in ipython and return the execution result.\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"cell\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"Valid Python cell to execute.\",\n",
    "                    }\n",
    "                },\n",
    "                \"required\": [\"cell\"],\n",
    "            },\n",
    "        },\n",
    "        {\n",
    "            \"name\": \"sh\",\n",
    "            \"description\": \"run a shell script and return the execution result.\",\n",
    "            \"parameters\": {\n",
    "                \"type\": \"object\",\n",
    "                \"properties\": {\n",
    "                    \"script\": {\n",
    "                        \"type\": \"string\",\n",
    "                        \"description\": \"Valid shell script to execute.\",\n",
    "                    }\n",
    "                },\n",
    "                \"required\": [\"script\"],\n",
    "            },\n",
    "        },\n",
    "    ],\n",
    "    \"config_list\": config_list,\n",
    "    \"timeout\": 120,\n",
    "}\n",
    "\n",
    "chatbot = CompressibleAgent(\n",
    "    name=\"chatbot\",\n",
    "    system_message=\"For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.\",\n",
    "    llm_config=llm_config,\n",
    "    compress_config={\n",
    "        \"mode\": \"COMPRESS\",\n",
    "        \"trigger_count\": 600,  # set this to a large number for less frequent compression\n",
    "        \"verbose\": True,  # set this to False to suppress the compression log\n",
    "        \"leave_last_n\": 2,\n",
    "    },\n",
    ")\n",
    "\n",
    "# create a UserProxyAgent instance named \"user_proxy\"\n",
    "user_proxy = autogen.UserProxyAgent(\n",
    "    name=\"user_proxy\",\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\") and x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
    "    human_input_mode=\"NEVER\",\n",
    "    max_consecutive_auto_reply=10,\n",
    "    code_execution_config={\n",
    "        \"work_dir\": \"coding\",\n",
    "        \"use_docker\": False,\n",
    "    },  # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
    ")\n",
    "\n",
    "\n",
    "def exec_python(cell):\n",
    "    ipython = get_ipython()\n",
    "    result = ipython.run_cell(cell)\n",
    "    log = str(result.result)\n",
    "    if result.error_before_exec is not None:\n",
    "        log += f\"\\n{result.error_before_exec}\"\n",
    "    if result.error_in_exec is not None:\n",
    "        log += f\"\\n{result.error_in_exec}\"\n",
    "    return log\n",
    "\n",
    "\n",
    "def exec_sh(script):\n",
    "    return user_proxy.execute_code_blocks([(\"sh\", script)])\n",
    "\n",
    "\n",
    "# register the functions\n",
    "user_proxy.register_function(\n",
    "    function_map={\n",
    "        \"python\": exec_python,\n",
    "        \"sh\": exec_sh,\n",
    "    }\n",
    ")\n",
    "\n",
    "# start the conversation\n",
    "user_proxy.initiate_chat(\n",
    "    chatbot,\n",
    "    message=\"Draw two agents chatting with each other with an example dialog. Don't add plt.show().\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 3\n",
    "This example is from [agent_chat_web_info.ipynb](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_web_info.ipynb). \n",
    "We use this example to demonstrate how to pass in a customized compression function. We pass in an compression function `constrain_num_messages`, which constrains the number of messages to be 3 or less. \n",
    "The customized function should accept a list of messages as input and return a tuple of `(is_success: bool, compressed_messages: List[Dict])`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to assistant):\n",
      "\n",
      "Show me the YTD gain of 10 largest technology companies as of today.\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33massistant\u001b[0m (to user_proxy):\n",
      "\n",
      "To fulfill your request, we first need a list of the 10 largest technology companies by market cap. Unfortunately, real-time financial data is gated behind paywalls, so it's difficult to get up-to-date reliable information through coding here. \n",
      "\n",
      "However, I can provide you a script to download YTD performance for a given list of companies if you already know the list. Below is an example using Yahoo Finance, for which Python has a usable API.\n",
      "\n",
      "Frequently mentioned largest technology companies include: Apple (AAPL), Microsoft (MSFT), Alphabet (GOOGL), Amazon (AMZN), Meta (FB), Tesla (TSLA), Alibaba group holding (BABA), Taiwan Semiconductor (TSM), Tencent (TCEHY), Oracle (ORCL). Adjust as necessary for your purpose.\n",
      "\n",
      "First, we need to install yfinance module:\n",
      "\n",
      "```sh\n",
      "pip install yfinance\n",
      "```\n",
      "\n",
      "Then, you can use this Python script to get the YTD performance. \n",
      "\n",
      "This Python script first gets the price at the beginning of the year, and then the most recent price. The difference between those two prices, divided by the starting price, gives the YTD performance.\n",
      "\n",
      "```python\n",
      "# filename: ytd_gain.py\n",
      "\n",
      "import yfinance as yf\n",
      "from datetime import datetime\n",
      "\n",
      "# Define the tickers\n",
      "tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'FB', 'TSLA', 'BABA', 'TSM', 'TCEHY', 'ORCL']\n",
      "now = datetime.now()\n",
      "\n",
      "# Define the current year\n",
      "current_year = now.year\n",
      "\n",
      "# Iterate through each ticker\n",
      "for ticker in tickers:\n",
      "    # Download the year-to-date data for this ticker\n",
      "    ticker_data = yf.download(ticker, start=f'{current_year}-01-01', end=f'{now.year}-{now.month}-{now.day}')\n",
      "\n",
      "    # Get the close price at the beginning of the year\n",
      "    initial_price = ticker_data['Close'][0]\n",
      "\n",
      "    # Get the most recent close price\n",
      "    most_recent_price = ticker_data['Close'][-1]\n",
      "\n",
      "    # Calculate the year-to-date return\n",
      "    ytd_return = (most_recent_price - initial_price) / initial_price * 100\n",
      "\n",
      "    # Print the result\n",
      "    print(f'The YTD return for {ticker} is {ytd_return:.2f}%')\n",
      "```\n",
      "\n",
      "Run the file in shell:\n",
      "\n",
      "```sh\n",
      "python ytd_gain.py\n",
      "```\n",
      "\n",
      "The output will be the YTD gain (%) of each company to the console. Please note that actual results will depend on the input list of tickers and the current date.\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[31m\n",
      ">>>>>>>> USING AUTO REPLY...\u001b[0m\n",
      "\u001b[31m\n",
      ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "execute_code was called without specifying a value for use_docker. Since the python docker package is not available, code will be run natively. Note: this fallback behavior is subject to change\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[31m\n",
      ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "execute_code was called without specifying a value for use_docker. Since the python docker package is not available, code will be run natively. Note: this fallback behavior is subject to change\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to assistant):\n",
      "\n",
      "exitcode: 1 (execution failed)\n",
      "Code output: \n",
      "Requirement already satisfied: yfinance in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (0.2.31)\n",
      "Requirement already satisfied: pandas>=1.3.0 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (2.1.2)\n",
      "Requirement already satisfied: numpy>=1.16.5 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (1.26.1)\n",
      "Requirement already satisfied: requests>=2.31 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (2.31.0)\n",
      "Requirement already satisfied: multitasking>=0.0.7 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (0.0.11)\n",
      "Requirement already satisfied: lxml>=4.9.1 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (4.9.3)\n",
      "Requirement already satisfied: appdirs>=1.4.4 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (1.4.4)\n",
      "Requirement already satisfied: pytz>=2022.5 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (2023.3.post1)\n",
      "Requirement already satisfied: frozendict>=2.3.4 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (2.3.8)\n",
      "Requirement already satisfied: peewee>=3.16.2 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (3.17.0)\n",
      "Requirement already satisfied: beautifulsoup4>=4.11.1 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (4.12.2)\n",
      "Requirement already satisfied: html5lib>=1.1 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from yfinance) (1.1)\n",
      "Requirement already satisfied: soupsieve>1.2 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)\n",
      "Requirement already satisfied: six>=1.9 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\n",
      "Requirement already satisfied: webencodings in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n",
      "Requirement already satisfied: tzdata>=2022.1 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from pandas>=1.3.0->yfinance) (2023.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from requests>=2.31->yfinance) (3.3.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from requests>=2.31->yfinance) (3.4)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from requests>=2.31->yfinance) (2.0.7)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages (from requests>=2.31->yfinance) (2023.7.22)\n",
      "\n",
      "ytd_gain.py:19: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  initial_price = ticker_data['Close'][0]\n",
      "ytd_gain.py:22: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  most_recent_price = ticker_data['Close'][-1]\n",
      "ytd_gain.py:19: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  initial_price = ticker_data['Close'][0]\n",
      "ytd_gain.py:22: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  most_recent_price = ticker_data['Close'][-1]\n",
      "ytd_gain.py:19: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  initial_price = ticker_data['Close'][0]\n",
      "ytd_gain.py:22: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  most_recent_price = ticker_data['Close'][-1]\n",
      "ytd_gain.py:19: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  initial_price = ticker_data['Close'][0]\n",
      "ytd_gain.py:22: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n",
      "  most_recent_price = ticker_data['Close'][-1]\n",
      "\n",
      "1 Failed download:\n",
      "['FB']: Exception('%ticker%: No timezone found, symbol may be delisted')\n",
      "Traceback (most recent call last):\n",
      "  File \"ytd_gain.py\", line 19, in <module>\n",
      "    initial_price = ticker_data['Close'][0]\n",
      "  File \"/home/ykw5399/.conda/envs/dev2/lib/python3.9/site-packages/pandas/core/series.py\", line 1037, in __getitem__\n",
      "    return self._values[key]\n",
      "IndexError: index 0 is out of bounds for axis 0 with size 0\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33massistant\u001b[0m (to user_proxy):\n",
      "\n",
      "There seems to be an issue with the ticker symbol 'FB'. The error message shows 'FB': No timezone found, symbol may be delisted. This is because 'FB' has been recently changed to 'META' so the symbol 'FB' is causing the issue.\n",
      "\n",
      "We can replace 'FB' with 'META' and rerun the Python script. The error messages for series indexing are warnings rather than errors. It seems recent changes in pandas library caused these warnings. To resolve this, `iloc` function will be utilized to get the first and last items of the Series.\n",
      "\n",
      "Here is the updated Python script:\n",
      "\n",
      "```python\n",
      "# filename: ytd_gain.py\n",
      "\n",
      "import yfinance as yf\n",
      "from datetime import datetime\n",
      "\n",
      "# Define the tickers\n",
      "tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'BABA', 'TSM', 'TCEHY', 'ORCL']\n",
      "now = datetime.now()\n",
      "\n",
      "# Define the current year\n",
      "current_year = now.year\n",
      "\n",
      "# Iterate through each ticker\n",
      "for ticker in tickers:\n",
      "    # Download the year-to-date data for this ticker\n",
      "    ticker_data = yf.download(ticker, start=f'{current_year}-01-01', end=f'{now.year}-{now.month}-{now.day}')\n",
      "\n",
      "    # Get the close price at the beginning of the year\n",
      "    initial_price = ticker_data['Close'].iloc[0]\n",
      "\n",
      "    # Get the most recent close price\n",
      "    most_recent_price = ticker_data['Close'].iloc[-1]\n",
      "\n",
      "    # Calculate the year-to-date return\n",
      "    ytd_return = (most_recent_price - initial_price) / initial_price * 100\n",
      "\n",
      "    # Print the result\n",
      "    print(f'The YTD return for {ticker} is {ytd_return:.2f}%')\n",
      "```\n",
      "\n",
      "Run the file in shell:\n",
      "\n",
      "```sh\n",
      "python ytd_gain.py\n",
      "```\n",
      "\n",
      "This script will print the YTD gain (%) of each company to the console.\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[31m\n",
      ">>>>>>>> USING AUTO REPLY...\u001b[0m\n",
      "\u001b[31m\n",
      ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "execute_code was called without specifying a value for use_docker. Since the python docker package is not available, code will be run natively. Note: this fallback behavior is subject to change\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[31m\n",
      ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is sh)...\u001b[0m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "execute_code was called without specifying a value for use_docker. Since the python docker package is not available, code will be run natively. Note: this fallback behavior is subject to change\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33muser_proxy\u001b[0m (to assistant):\n",
      "\n",
      "exitcode: 0 (execution succeeded)\n",
      "Code output: \n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for AAPL is 41.24%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for MSFT is 47.26%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for GOOGL is 44.86%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for AMZN is 61.50%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for META is 152.20%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TSLA is 103.48%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for BABA is -7.25%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TSM is 23.99%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TCEHY is -12.16%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for ORCL is 29.06%\n",
      "\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for AAPL is 41.24%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for MSFT is 47.26%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for GOOGL is 44.86%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for AMZN is 61.50%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for META is 152.20%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TSLA is 103.48%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for BABA is -7.25%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TSM is 23.99%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for TCEHY is -12.16%\n",
      "\n",
      "[*********************100%%**********************]  1 of 1 completed\n",
      "The YTD return for ORCL is 29.06%\n",
      "\n",
      "\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[35mToken Count (including 468 tokens from system msg and function descriptions). Before compression : 3684 | After: 1509\u001b[0m\n",
      "--------------------------------------------------------------------------------\n",
      "\u001b[33massistant\u001b[0m (to user_proxy):\n",
      "\n",
      "It appears that you've run the script twice, as the results are duplicated. From the code output, here are the Year-to-Date (YTD) returns for the 10 largest technology companies:\n",
      "\n",
      "1. AAPL (Apple): 41.24% gain\n",
      "2. MSFT (Microsoft): 47.26% gain\n",
      "3. GOOGL (Alphabet Class A): 44.86% gain\n",
      "4. AMZN (Amazon): 61.50% gain\n",
      "5. META (Meta Platforms, previously Facebook): 152.20% gain\n",
      "6. TSLA (Tesla): 103.48% gain\n",
      "7. BABA (Alibaba): -7.25% loss\n",
      "8. TSM (Taiwan Semiconductor Manufacturing): 23.99% gain\n",
      "9. TCEHY (Tencent Holdings): -12.16% loss\n",
      "10. ORCL (Oracle): 29.06% gain\n",
      "\n",
      "Please note, the percentage change could have slight differences due to market volatility and the exact times the prices were taken. \n",
      "\n",
      "If everything looks good, let's wrap up. If you need any more help with other tasks, just let me know! \n",
      "\n",
      "TERMINATE\n",
      "\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "def constrain_num_messages(messages):\n",
    "    \"\"\"Constrain the number of messages to 3.\n",
    "\n",
    "    This is an example of a customized compression function.\n",
    "\n",
    "    Returns:\n",
    "        bool: whether the compression is successful.\n",
    "        list: the compressed messages.\n",
    "    \"\"\"\n",
    "    if len(messages) <= 3:\n",
    "        # do nothing\n",
    "        return False, None\n",
    "\n",
    "    # save the first and last two messages\n",
    "    return True, messages[:1] + messages[-2:]\n",
    "\n",
    "\n",
    "# create a CompressibleAgent instance named \"assistant\"\n",
    "assistant = CompressibleAgent(\n",
    "    name=\"assistant\",\n",
    "    llm_config={\n",
    "        \"timeout\": 600,\n",
    "        \"cache_seed\": 43,\n",
    "        \"config_list\": config_list,\n",
    "    },\n",
    "    compress_config={\n",
    "        \"mode\": \"CUSTOMIZED\",\n",
    "        \"compress_function\": constrain_num_messages,  # this is required for customized compression\n",
    "        \"trigger_count\": 1600,\n",
    "    },\n",
    ")\n",
    "\n",
    "# create a UserProxyAgent instance named \"user_proxy\"\n",
    "user_proxy = autogen.UserProxyAgent(\n",
    "    name=\"user_proxy\",\n",
    "    human_input_mode=\"TERMINATE\",\n",
    "    max_consecutive_auto_reply=10,\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\")\n",
    "    or x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE.\"),\n",
    "    code_execution_config={\n",
    "        \"work_dir\": \"web\",\n",
    "        \"use_docker\": False,\n",
    "    },  # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
    "    system_message=\"\"\"Reply TERMINATE if the task has been solved at full satisfaction.\n",
    "Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\"\"\",\n",
    ")\n",
    "\n",
    "user_proxy.initiate_chat(\n",
    "    assistant,\n",
    "    message=\"\"\"Show me the YTD gain of 10 largest technology companies as of today.\"\"\",\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "msft",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.18"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
